#include <stdlib.h>

#include <windows.h>

#include "lib.h"

#include "vpconn.h"
#include "vpgame.h"
#include "vpguts.h"
#include "vpopts.h"
#include "log.h"

#include "stdncerr.h"
#include "badpacketmsgs.h"

#include "VPGameBase.h"

#include "opts.h"
#include "VPXMLTranslator.h"

VPConnection::VPConnection(ProgressiveServer* server, SOCKET client)
    : ProgressiveConnection(server, client)
{
    flags = 0;
    machId = -1;
    creditValue = 0;
    payTableFreq = 0;
    compFactor = 0;
    game = (VPGame*)0;
    dblUpEnabled = true;
	dlbUpCapAmount = 0;

    casinoSpecific = (void*)0;
    
    subGameLength  = 0;  //default subgame data length to zero

    // kharmon.  03-22-06.  #10515.  New member variables related to 7 stud poker implementation.
    m_pGameDBInterface      = NULL;
    m_bSideBetEnabled       = false;  
    m_ucSideBetPayTable     = 0;
    m_ucDeckConfiguration   = 0;

    m_eGameID          = VP_FIVE_CARD;
    m_ucProtoVer1      = 0;
    m_ucProtoVer2      = 0;

    // kharmon.  11-15-06.  #16921.  Zero out buffers before using them.
    msglen = 0;
    memset(msgbuf, 0, sizeof(msgbuf));
}

VPConnection::~VPConnection()
{
    // save game is always up to date at this point
    //

    delete game;
    game = (VPGame*)0;
    machId = -1;
    creditValue = 0;
    payTableFreq = 0;
    flags &= ~LOGGEDON;

	// CSN-7075: New Authentication
	trackLogoutSession();

    // kharmon. 03-26-06.  #10515.
    delete m_pGameDBInterface;
    m_pGameDBInterface = NULL;

    removeCasinoSpecific();
}

void
VPConnection::msgStart()
{
    msglen = 0;
}

void
VPConnection::msgAdd(uint8 Byte)
{
    msgbuf[msglen++] = Byte;
}

void
VPConnection::msgAdd(int32 SignedLong)
{
    msgbuf[msglen++] = (uint8)((SignedLong) & 0xff);
    msgbuf[msglen++] = (uint8)(((SignedLong) >> 8) & 0xff);
    msgbuf[msglen++] = (uint8)(((SignedLong) >> 16) & 0xff);
    msgbuf[msglen++] = (uint8)(((SignedLong) >> 24) & 0xff);
}

uint8*
VPConnection::msgGetBuf()
{
    return msgbuf + msglen;
}

void
VPConnection::msgReleaseBuf(int32 Amt)
{
    msglen += Amt;
}

void
VPConnection::msgEnd()
{
    msglen = encode(msgbuf, obuf + oused + 1, msglen);
    msglen++;    // include msg len byte
    obuf[oused] = (uint8)(msglen & 0xff);
    oused += msglen;
}

void VPConnection::Answer( uint8 ans )
{
    msgStart();
    msgAdd(ans);
    msgEnd();
}

void VPConnection::Error( int err )
{
    msgStart();
    msgAdd((uint8)'E');
    msgAdd((uint8)(err & 0xff) );
    msgEnd();
}


void
VPConnection::HandlePlayerUnknown()    //Unknown
{
    Log::msgWarning("Connection::getResponse:  Unknown input (0x%02x)  ",
        msgbuf[0]);
        
    trackaddBadPacket(GETREPONSE_DEFAULT_CASE,msgbuf[0],(char*)msgbuf,msglen);

    Answer('N');
}


void
VPConnection::sendStopMsg()
{
    msgStart();
    msgAdd((uint8)'!');
    msgEnd();
}

bool8
VPConnection::getResponse()
{
    int32 ieat = 0, remain = iused, len;
    uint8* src = ibuf;

	// [AO 2007-10-01] We can send just one reply for certain repeating messages.
	// We're not handling partial buffers and incomplete messages but that's OK.
	char last_message = 0;

    while (remain > 0)
    {
        if (remain < src[0])
            break;

        // Handle an invalidly formatted message of length zero.
        if (src[0] <= 1)
        {
            ieat += 1;
            remain -= 1;
            src += 1;
            continue;
        }

        // kharmon.  11-15-06.  #16921.  Zero out buffers before using them.
	    msglen = 0;
	    memset(msgbuf, 0, sizeof(msgbuf));

        len = decode(src + 1, msgbuf, src[0] - 1);
        ieat += src[0];
        remain -= src[0];
        src += src[0];

		switch (msgbuf[0])
        {
            case 'L':    HandlePlayerAnsiLogin    ();    break;    //Login
            case 'M':    
            {
                switch (msgbuf[1])
                {
                    case 'L':    HandlePlayerUnicodeLogin (); break;    //Login
                    default:    HandlePlayerUnknown         (); break;    //Unknown
                }
                break;
            }

			case 'R':	// Reload with a new denomination (CSN-13423)
			{
				if (!(flags & LOGGEDON))
                {
                    Log::msgWarning("Reload: not logged on... cannot reload with a new denomination");

					trackaddBadPacket(TRY_RELOAD_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

                    Answer('B');

                    break;
                }

                if (isServerStopping())
                {
                    sendStopMsg();
                    break;
                }

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;
				
				if (game)
				{
					// We have to delete previous game (it was played with a different denomination)
					// If there is a saved game, it will ve recovered later
					delete game;
					game = (VPGame*)0;
				}
				
				HandleReloadDenomination();		
				break;
			}

            case 'D':    // Deal
            {
                if (!(flags & LOGGEDON))
                {
                    Log::msgWarning("Deal: not logged on...invalid bet  ");

					trackaddBadPacket(TRY_DEAL_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

                    Answer('B');

                    break;
                }

                if (isServerStopping())
                {
                    sendStopMsg();
                    break;
                }

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				if (!game)
                {
                    int32 result = VPGameBase::DEAL_CREATE_GAME_FAILURE;

                    // kharmon.  03-22-06.  #10515.  Reworked this so that more of the
                    // message processing is handled inside the game objects.
                    game = VPGameBase::createGame(this, m_pGameDBInterface);

                    if (game && game->initOk())
                    {
                        result = game->handleMessage(msgbuf, len);    // creates result msg if successful
                    }
                    else
                    {
                        delete game;
                        game = (VPGame*)0;
                    }

                }
                else
                {
                    DEBUGMSG(("  game in progress  "));

                    Answer('G');
                }
                break;
            }

#ifdef ALLOW_HAND_TEST
            case 't':    // 't'est hand
            {
                extern void handtest(uint8*, VPConnection*);
                handtest(msgbuf, this);
                break;
            }
#endif

			case 'm':  // Balance Request
            {
				// [AO 2007-10-01] Ignore repeating messages
				if (msgbuf[0] == last_message)
					break;

                int32 balance;

                if (isServerStopping())
                {
                    sendStopMsg();
                    break;
                }

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				//rule1: gotta be logged on
                if( (flags & LOGGEDON) && trackGetBalance(&balance) )
                {
                    msgStart();
                    msgAdd((uint8)'m');
                    msgAdd((int32)balance);
                    msgEnd();
                }

                break;
            }

            default:
            {
                // kharmon.  03-22-06.  #10515.  Handle all game specific messages except "Deal" here.
                if (!(flags & LOGGEDON))
                {
                    Log::msgWarning("VP: not logged on  ");

					trackaddBadPacket(TRY_ACTION_NOT_LOGGED_ON, msgbuf[0], (char*)msgbuf, len);

                    Answer('N');
                    break;
                }

                if(!game)
                {
					trackaddBadPacket(TRY_DBLUPDENY_NOT_LOGGED_ON, msgbuf[0], (char*) msgbuf, len);

                    Answer('N');
                    break;
                }

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				// let the game object try to process the message
                if(!game->handleMessage(msgbuf, len))
                {
                    // hmmmm...the game object didn't know what to make of it.  
                    // Report accordingly.
                    HandlePlayerUnknown();                
                }

                break;
            }
        }

		last_message = msgbuf[0]; // [AO 2007-10-01] Handle repeating messages
    }

	if (iused <= ieat) // [AO 2007-10-01] Avoid buffer overflow
        iused = 0;
    else
    {
        MoveMemory(ibuf, ibuf + ieat, iused - ieat);
        iused -= ieat;
    }

    return (bool8)(oused != 0);
}

void
VPConnection::gameOver()
{
    delete game;
    game = (VPGame*)0;

    // if saved game used a different pay table than the current
    // setting for this machine, notify client of new paytable
    //
    if (flags & (PAYTABLECHANGED | DBLUPCHANGED | SIDEBETPAYTABLECHANGED | SIDEBETENABLEDCHANGED))
    {
        msgStart();
        msgAdd((uint8)'O');        // 'O'ptions
        msgAdd((uint8)((flags & PAYTABLECHANGED) != 0));
        msgAdd((uint8)(payTableFreq & 0xFFL));

        msgAdd((uint8)((flags & DBLUPCHANGED) != 0));
        msgAdd((uint8)(dblUpEnabled != 0));

        if(m_ucProtoVer1>=2)
        {
            // kharmon.  03-22-06.  #10515.  Support for side bets needed by 7 stud poker, but available to all.

            // if the side bet paytable changed, alert the client.
            msgAdd((uint8)((flags & SIDEBETPAYTABLECHANGED) != 0));
            msgAdd((uint8)(m_ucSideBetPayTable & 0xFFL));
            
            // if the side bet has been turned on or off, alert the client.
            msgAdd((uint8)((flags & SIDEBETENABLEDCHANGED) != 0));
            msgAdd(m_bSideBetEnabled);
        }

        msgEnd();

        flags &= ~(PAYTABLECHANGED | DBLUPCHANGED | SIDEBETPAYTABLECHANGED | SIDEBETENABLEDCHANGED);
    }
}

void
VPConnection::HandlePlayerAnsiLogin()
{
    int32 err = 0;

    if (!parseAnsiCasinoLogin(&err))
    {
        Error(err);
        return;
    }

    HandlePlayerLogin();
}

void
VPConnection::HandlePlayerUnicodeLogin()
{
    int32 err = 0;

    if (!parseUnicodeCasinoLogin(&err))
    {
        Error(err);
        return;
    }

    HandlePlayerLogin();
}

void 
VPConnection::HandleReloadDenomination()	// CSN-13423: change denomination in VPoker
{
	int32 err = 0;

    if (!parseReloadDenomination(&err))
    {
        Error(err);
        return;
    }

    HandlePlayerLogin(true);
}

void
VPConnection::HandlePlayerLogin(bool isReload)    //Login
{
    int32 err = 0;

    if (isServerStopping())
    {
        machId = -1;
        sendStopMsg();
        return;
    }

    //must not be logged in	
    if( (flags & LOGGEDON) )
    {
		// CSN-13423: if we are reloading, we are already logged in
		if (!isReload)
		{			
			Log::msgWarning("Login: attempt to re-log on  ");

			trackaddBadPacket(TRY_LOGON_WHEN_LOGGED_ON,msgbuf[0],(char*)msgbuf,msglen);

			machId = -1;
			Answer('N');
			return;
		}
    }

    //
    //so the user is not logged into the casino
    //
    VPokerOpts opts;

    if (!trackGetOptions(&opts, (uint8) machId, creditValue) ||
        !trackGetPlayerLimits((uint8) machId, &compFactor))
    {
        Answer('N'); 

        machId = -1;
        creditValue = 0;
        return;
    }

    // kharmon.  03-22-06.  #10515.  New configuration options.
    m_bSideBetEnabled       = opts.bSideBetEnabled;
    m_ucSideBetPayTable     = opts.ucSideBetPayTable;
    m_ucDeckConfiguration   = opts.ucDeckConfiguration;

	setProgressiveOn(false);    

    payTableFreq = opts.payFreq;
    dblUpEnabled = opts.dblUpEnabled;
	dlbUpCapAmount = opts.dlbUpCapAmount;

	// psharpless #15914  
	// Credit value changes, no longer hardcoded

    // only the 5 card games care about the credit values, 
    // so most of this check only applies to them.
    if (m_eGameID!=VP_FIVE_CARD || 
        (VPGuts::getGuts(machId, payTableFreq) &&
		(creditValue == opts.anCreditValues[0] || 
		 creditValue == opts.anCreditValues[1] ||
		 creditValue == opts.anCreditValues[2] ||
		 creditValue == opts.anCreditValues[3] ||
		 creditValue == opts.anCreditValues[4])))
    {
		//if we are not saving fun play game data, do not attempt to load
		//saved fun play game data
		if(shouldSaveGameData())
		{
			// check for any saved games this user might have left around:
			if (!m_pGameDBInterface->processSavedGame(&game, &err) && err)
			{
				// error loading saved game...
				Error(err);
				return;
			}
		}

        int32 balance;
        if( !trackGetBalance(&balance) )
        {
            Answer('N');
            return;
        }

        msgStart();
        if (isReload)
			msgAdd((uint8)'R');
		else
			msgAdd((uint8)'L');
        msgAdd(balance);
        msgAdd((uint8)getMaxBet());
        msgAdd((uint8)(payTableFreq & 0xFFL));

        if(m_ucProtoVer1>=2)
        {
            // kharmon.  03-22-06.  #10515.
            // going forward, we'll send down the side bet info, and also a 
            // list of the supported credit values for games.
            msgAdd((uint8)(m_ucSideBetPayTable & 0xFFL));
            msgAdd(m_bSideBetEnabled);

			msgAdd(opts.anCreditValues[0]);
			msgAdd(opts.anCreditValues[1]);
			msgAdd(opts.anCreditValues[2]);
			msgAdd(opts.anCreditValues[3]);
			msgAdd(opts.anCreditValues[4]); 
		}
		else
		{
			// CSN-13423: flash client (protocol version 1) needs credits as well

			// Fixed number of denominations, as anCreditValues is a fixed array...
			msgAdd((int32)5);			
			msgAdd(opts.anCreditValues[0]);
			msgAdd(opts.anCreditValues[1]);
			msgAdd(opts.anCreditValues[2]);
			msgAdd(opts.anCreditValues[3]);
			msgAdd(opts.anCreditValues[4]);        
		}

        msgAdd((uint8)(game ? 1 : 0));

        if (game)
        {
            game->send();
        }

        msgEnd();

        if (game)
        {
            msgStart();
            msgAdd((uint8)'S');
            for (int i = 0; i<subGameLength && i<MAX_SUBGAME_DATA; i++)
            {
                msgAdd(subGameData[i]);
            }
            msgEnd();
        }

        //
        // prepare to notify client if saved game used a
        // different pay table or dblUpEnabled option than
        // the current setting
        //
        if (game)
        {
            if(payTableFreq != game->getPayFreq())                
            {
                flags |= PAYTABLECHANGED;                
            }

            if (game && dblUpEnabled != game->getDoubleUpEnabled())
            {
                flags |= DBLUPCHANGED;
            }

            // kharmon.  03-22-06.  #10515.  Support for side bets.
            if(m_ucSideBetPayTable != game->getSideBetPayTable())
            {
                flags |= SIDEBETPAYTABLECHANGED;
            }

            if(m_bSideBetEnabled != game->getSideBetEnabled())
            {
                flags |= SIDEBETENABLEDCHANGED;
            }

        }

        flags |= LOGGEDON;
    }
    else
    {
        Log::msgWarning("Login: bad vpoker params: %c, %d",
            machId, creditValue);

        Answer('N');

        machId = -1;
        creditValue = 0;
    }
}

// kharmon.  03-22-06.  #10515.  Used by Progressive stuff.
bool8 VPConnection::getForRealFlag()
{
	pInfo info = (pInfo) casinoSpecific;

    if (info && info->forMoney)
        return true;
    else 
        return false;
}

void	
VPConnection::allocateXMLTranslator( XMLTranslator** target )
{
	*target = new VPXMLTranslator( getServer(), this );
}

// [AO 2008-01-16] Refactoring for HA
bool VPConnection::ValidateSession()
{
	int32 session_check = trackValidateSession();
	if (!session_check) // session is valid
		return true;

	msgStart();
	msgAdd(static_cast<uint8>('N'));
	msgAdd(static_cast<int32>(session_check));
	msgEnd();
	return false;
}
